# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: leon

import datetime
import logging
import json
from enum import IntEnum
from functools import partial
import os
import time
import traceback
from concurrent.futures import ThreadPoolExecutor

from tornado import gen
from tornado.ioloop import IOLoop, PeriodicCallback
from absl import app, flags
import requests
import MySQLdb

from coin.base.hostname import get_hostname
from coin.exchange.util.async_http_client import AsyncHttpClient
from coin.exchange.okex_futures.kr_rest.futures_product import OkexFuturesProduct
from coin.exchange.okex_futures.kr_rest.currency import OkexCurrency

FLAGS = flags.FLAGS

INSTRUMENT_SYMBOLS = ['f_usd_btc', 'f_usd_ltc', 'f_usd_eth', 'f_usd_bch', 'f_usd_eos']

REST_SYMBOLS = ['btc_usd', 'ltc_usd', 'eth_usd', 'bch_usd', 'eos_usd']

CONTRACT_TYPES = ['this_week', 'quarter']

CONTRACT_ID_DICT = {}

db_config_dict = None

machine = None

http_client = AsyncHttpClient()

executor = ThreadPoolExecutor(max_workers=len(INSTRUMENT_SYMBOLS))

flags.DEFINE_string('machine', None, 'Machine name.')

flags.DEFINE_bool('print_result', False, 'If print result when running.')

flags.DEFINE_string('db_config', None, 'Database config file.')


class BlastingOrderStatus(IntEnum):
  OPEN = 0
  FILLED = 1


def _generate_future_ticker_url(symbol, contract_type):
  return 'https://www.okex.com/api/v1/future_ticker.do?' \
         'symbol={symbol}&contract_type={contract_type}'.format(
             symbol=symbol, contract_type=contract_type)


def _generate_liquidation_url(contract_id, status: BlastingOrderStatus):
  return 'https://www.okex.com/v2/futures/pc/public/blastingOrders.do?' \
         'status={status}&currentPage=1&contractId={contract_id}&page_Length=50'.\
             format(status=status, contract_id=contract_id)


def _generate_top_trader_sentiment_index_url(instrument_symbol):
  return 'https://www.okex.com/v2/futures/pc/public/eliteScale.do?symbol=' \
         '{symbol}&type=0&random=7'.format(symbol=instrument_symbol)


def _generate_top_trader_avg_margin_used_url(instrument_symbol):
  return 'https://www.okex.com/v2/futures/pc/public/getFuturePositionRatio.do?' \
         'symbol={symbol}&type=0&random=89'.format(symbol=instrument_symbol)


def _generate_delivery_url(instrument_symbol):
  return 'https://www.okex.com/v2/futures/pc/public/futuresDelivery.do?' \
         'currentPage=1&symbol={symbol}&type=0'.format(symbol=instrument_symbol)


def _generate_insurance_fund_url(instrument_symbol):
  return 'https://www.okex.com/v2/futures/pc/public/futureRiskPreparation.do?' \
         'symbol={symbol}&type=0'.format(symbol=instrument_symbol)


def _query_liquidation(contract_id, status):
  url = _generate_liquidation_url(contract_id, status)
  return http_client.post(url)


def _query_top_trader_sentiment_index(instrument_symbol):
  url = _generate_top_trader_sentiment_index_url(instrument_symbol)
  return http_client.get(url)


def _query_top_trader_avg_margin_used(instrument_symbol):
  url = _generate_top_trader_avg_margin_used_url(instrument_symbol)
  return http_client.get(url)


def _query_delivery(instrument_symbol):
  url = _generate_delivery_url(instrument_symbol)
  return http_client.post(url)


def _query_insurance_fund(instrument_symbol):
  url = _generate_insurance_fund_url(instrument_symbol)
  return http_client.post(url)


@gen.coroutine
def execute_every_5sec():
  for status in [BlastingOrderStatus.OPEN, BlastingOrderStatus.FILLED]:
    for key, contract_id in CONTRACT_ID_DICT.items():
      pair, contract_type = key.split('.')
      product = OkexFuturesProduct.FromCurrencyPairAndContractType(pair, contract_type)
      resp = yield _query_liquidation(contract_id, status)
      yield async_push_to_db(product, 'liquidation', resp.json())


@gen.coroutine
def execute_per_currency(func, column):
  for symbol in INSTRUMENT_SYMBOLS:
    currency = symbol.split('_')[-1]
    currency = OkexCurrency.FromStrNativeCurrency(currency)
    resp = yield func(symbol)
    yield async_push_to_db(currency, column, resp.json())
    logging.info('Query %s for %s on %s.' % (column, currency.symbol, machine))


def _gen_sql(product_type, symbol, column, json_obj):
  if FLAGS.print_result:
    print(product_type, symbol, column, json_obj)

  sql = '''
  INSERT OkexInstrumentFeed(
      trading_date, machine, product_type, symbol, instrument_column_name, fetch_time, json)
  VALUES (%s, %s, %s, %s, %s, %s, %s)
  '''

  now = datetime.datetime.now()
  trading_date = now.strftime('%Y-%m-%d')
  fetch_time = now.strftime('%Y-%m-%d %H:%M:%S.%f')

  values = (trading_date, machine, product_type, symbol, column, fetch_time, json.dumps(json_obj))
  return (sql, values)


def sync_push_to_db(product, column, json_obj):
  conn = MySQLdb.connect(host=db_config_dict.get('host', None),
                         db=db_config_dict.get('database', None),
                         user=db_config_dict.get('user', None),
                         passwd=db_config_dict.get('password', None))
  cursor = conn.cursor()

  try:
    if product == 'FUTURES_ALL':
      product_type = 'Futures'
      symbol = 'ALL'
    elif product is not None:
      product_type = product._product_type
      symbol = product.symbol
    else:
      raise ValueError(product)
    sql, values = _gen_sql(product_type, symbol, column, json_obj)
    cursor.execute(sql, values)
    conn.commit()
  except Exception as e:
    logging.error('Fail to dump to db.', type(e), e)
    logging.error(traceback.format_exc())
    conn.rollback()
  finally:
    conn.close()


def async_push_to_db(product, column, json_obj):
  return executor.submit(sync_push_to_db, product, column, json_obj)


def _sync_query_contract_id(symbol, contract_type):
  url = _generate_future_ticker_url(symbol, contract_type)
  resp = requests.get(url=url).json()
  return resp['ticker']['contract_id']


def sync_update_contract_ids():
  CONTRACT_ID_DICT.clear()
  for symbol in REST_SYMBOLS:
    for contract_type in CONTRACT_TYPES:
      contract_id = _sync_query_contract_id(symbol, contract_type)
      time.sleep(1)
      CONTRACT_ID_DICT['%s.%s' % (symbol, contract_type)] = contract_id
  sync_push_to_db('FUTURES_ALL', 'contract_id_info', CONTRACT_ID_DICT)


def setup_ioloop_callback(ioloop):
  PeriodicCallback(execute_every_5sec, 5 * 1000).start()
  PeriodicCallback(
      partial(execute_per_currency,
              _query_top_trader_sentiment_index,
              'long_short_position_account_ratio'),
      1 * 60 * 1000).start()
  PeriodicCallback(
      partial(execute_per_currency, _query_top_trader_avg_margin_used, 'long_short_margin_ratio'),
      1 * 60 * 1000).start()
  PeriodicCallback(partial(execute_per_currency, _query_delivery, 'delivery'),
                   60 * 60 * 1000).start()
  ioloop.add_timeout(datetime.timedelta(hours=1),
                     partial(execute_per_currency, _query_insurance_fund, 'insurance_fund'))


def main(_):
  global machine
  machine = FLAGS.machine or get_hostname()

  db_config = FLAGS.db_config
  assert db_config, '--db_config must be specified.'
  global db_config_dict
  db_config_path = os.path.expanduser(FLAGS.db_config)
  assert db_config_path and os.path.isfile(db_config_path), \
      'DB config file does not exist: %s' % db_config_path
  with open(db_config_path, 'r') as open_file:
    db_config_dict = json.load(open_file)

  ioloop = IOLoop.current()
  ioloop.add_timeout(datetime.timedelta(hours=23, minutes=59), ioloop.stop)

  while True:
    try:
      sync_update_contract_ids()
      break
    except Exception:
      logging.exception('Update contract ids failed. Reconnecting in 10 seconds...')
      time.sleep(10)

  setup_ioloop_callback(ioloop)
  ioloop.start()


if __name__ == '__main__':
  logging.basicConfig(level='DEBUG', format='%(levelname)8s %(asctime)s %(name)s] %(message)s')

  app.run(main)
